"Animation can explain whatever the mind of man can conceive. This facility makes it the most versatile and explicit means of communication yet devised for quick mass appreciation."
—Walt Disney
Walt Disney talked about cartoon animation with the voice of a true pioneer. Much of what he said rings true today to the new pioneers in the field of computer animation. Multimedia is touted as the education tool of the next century. Even the lowly
computer game is aspiring to Interactive Fiction, but the difference is that the animated tales are not only coming to life, they are responding to our presence and bringing us into their yarns; into a place where whole worlds can be created with the flick
of an artist's stylus.
Visual Basic is at the heart of it. No other tool offers both the broad flexibility of a true programming language and the development speed of an authoring tool. While Visual Basic interfaces with many, many third-party controls, utilities, and
applications, you will concentrate, in this chapter, on the innate abilities of Visual Basic itself, particularly those that are new with Visual Basic 4.0.
While most of what is covered here will seem most directly applicable to games, it is equally well suited for a variety of graphical applications. The techniques, once learned, are simple enough to be included almost anywhere.
Have you ever seen a flip-book? I used to make them all the time as a kid. You just take any sufficiently fat tablet and draw a different frame of animation on each page until you have filled them all (or your hand gets very tired, whichever comes
first).
Then you use your thumb to slowly, steadily flip through the pages to produce animation. The pictures run together to produce what looks like true movement.
In the animation industry, this type of animation didn't last long. How could you possibly produce a ten-minute short, let alone an hour-and-a-half movie, when you had to draw each frame of animation from scratch. It couldn't be done.
But wait. Why redraw the whole frame? Not everything moves with every frame. In fact, from one frame to the next, very few things move. So why not just move the things that move and leave the rest of it alone! This idea brought forth what animators call
cels. Cels are clear plastic sheets laid one on top of the other, each with a component of the animated scene. Whenever one of the components moved, only that cel had to be redrawn and replaced.
So it is today in the computer world. If you want to create animation, you could simply redraw or replace the entire picture fifteen times a second, but that would produce far more work for the computer than is necessary, and your animations would be
slow and choppy, even on the fastest of machines.
Today, multimedia and game developers use a technique very similar to the cartoonist's cel to optimize their animations. The technique utilizes something called sprites.
What is a sprite? A sprite is an on-screen entity that moves around over a background bitmap without disturbing it. In addition, there are often a number of these guys on a screen at once. The programmer occupies much of his time deciding how they
should move, what they should look like, how they should interact, what should happen if they bump into each other, and so forth. A sprite, in short, is a name for all the little people who inhabit your graphical world.
So where are the sprite facilities in Visual Basic. or in Windows, or in the VGA video card? It is such a basic concept, that you would think that these environments would provide all kinds of routines and data structures to support it. Unfortunately,
that's not the case. Game platforms do have these facilities at the hardware level. Even my old Commodore 64 understood sprites, but PCs have not traditionally been where it's at for hot games or graphics in general. Only recently with the near domination
of the PC platform, combined with the coming of age of the hardware, have serious graphics and game development moved to the PC. So only now are you starting to see more and more support of it at the various levels.
For Visual Basic, version 4.0 provides the much-needed PaintPicture procedure and the ImageList control and its associated Overlay and Draw functions (we'll talk about these later this chapter). Although there is not automatic, built-in support, you can
get there with a few objects and functions of your own.
All right, so what does it take to make a sprite. Well, the first condition is that it be a bitmap. No problem, but it also must be able to move around on top of a background bitmap without disturbing it.
One solution might be to create a picture control and just move the picture control around on top of another, larger picture control using the Move method. Because the two are separate controls, this works and the background isn't disturbed.
This would work except for two objections.
If you're writing a game with a warehouse setting with many boxes, maybe, but in most situations, you will not have square sprites. In fact, some folks include the words "irregularly shaped" in their definition of a sprite.
Great. So to get past the first objection, that this would be too slow, you traditionally turn to the Windows API or Win32. This was done in previous versions of Visual Basic with the BitBlt and StretchBlt functions mentioned earlier. Today, you can
turn to the PaintPicture method.
PaintPicture is an exciting new method included with Visual Basic 4.0. If you have done animation in earlier versions of Visual Basic for Windows 3.1 you have probably become quite familiar with the Windows API function BitBlt (pronounced bit-blit) and
its cousin StretchBlt. When you are doing animation in any language, it is essential to be able to move chunks of memory from one location to another very quickly; this is BitBlt's forte. StretchBlt does the same thing, but offers more options, like
reversing the image horizontally or vertically or, as the name implies, stretching the image out or squishing it down to a different size.
Although Win32 has new (and much faster) versions of BitBlt and StretchBlt, you probably will never need to visit them yourself. That's because the PaintPicture method provides an easy way to access these features without leaving the safety and comfort
of Visual Basic syntax.
Speaking of syntax, here is the syntax for PaintPicture.
DestinationObject.PaintPicture SourcePicture, DestinationX, DestinationY, DestinationWidth, DestinationHeight, _ SourceX, SourceY, SourceWidth, SourceHeight, _ BitwiseOperator
DestinationObject is either a form, a picture control, or the printer object. If omitted, the current form is the default.
SourcePicture is where the graphic to be painted on the destination will come from. Notice that it is a picture, not an object. Be sure and specify the Picture property of the form or picture control you want to use. This argument is, of course,
required.
DestinationX and DestinationY are the coordinates on the DestinationObject where you want this picture painted. These are also required.
DestinationWidth and DestinationHeight describe the space into which you want to place the picture. They are used in case you want to change the size of the picture from what it was originally (to stretch or shrink it).
SourceX, SourceY, SourceWidth, and SourceHeight are used to specify a clipping region or smaller portion of the source to be transferred to the destination. It's just like clipping a coupon out of the middle of a newspaper page (except that the page
stays intact!).
BitwiseOperator, also called a raster operation or ROP, is a long value that specifies how you want to copy the source to the destination. Table 10.1 shows possible values for BitwiseOperator and a description of each. These constants are defined in
WIN32API.TXT, which you can include as a module in your project.
Name |
Hex |
Decimal |
Operation |
SRCCOPY |
&HCC0020& |
13369376 |
Copies Source directly over Destination |
SRCPAINT |
&HEE0086& |
15597702 |
Bitwise OR of Source and Destination |
SRCAND |
&H8800C6& |
8913094 |
Bitwise AND of Source and Destination |
SRCINVERT |
&H660046& |
6684742 |
Bitwise XOR of Source and Destination |
SRCERASE |
&H440328& |
4457256 |
Source AND (NOT Destination) |
NOTSRCCOPY |
&H330008& |
3342344 |
NOT Source |
NOTSRCERASE |
&H1100A6& |
1114278 |
NOT (Source OR Destination) |
MERGEPAINT |
&HBB0226& |
12255782 |
(NOT Source) OR Destination |
DSTINVERT |
&H550009& |
5570569 |
NOT Destination |
BLACKNESS |
&H000042& |
66 |
Fill Destination with black |
WITENESS |
&HFF0062& |
16711778 |
Fill Destination with white |
Note that the ScaleMode used for every one of the coordinates and width/height arguments is that of the DestinationObject. It would be quite confusing if you have a different ScaleMode for the source. In general, when working with bitmaps and animation
at this level, it is best to standardize on a ScaleMode Pixel for everything.
If you read a book like I do, you just skipped that last section and jumped down to this one looking for example code. Although I have tried to make the preceding description of the syntax as meaningful as possible, nothing brings it home like seeing
real code. So here it is.
picBoard.PaintPicture picPlayer.picture, 284, 84, _ picPlayer.ScaleWidth, picPlayer.ScaleHeight, _ 0, 0, picPlayer.ScaleWidth, picPlayer.ScaleHeight, _ SRCCOPY
What does this one do? Well, imagine a game board with graphics on it. You want to copy the representation of the player onto the game board. The game board is in the picture control picBoard, and the representation of the player is in another picture
control picPlayer, so you execute the method of picBoard, because this will be your destination. The first argument specifies the source. You want to place the player at a particular position on the board, so you specify the X and Y pixel locations
(assuming everything is in ScaleMode Pixel). Then you say that you are going to take up a certain amount of space on the destination indicated by the picPlayer's ScaleWidth and ScaleHeight properties. Finally, you get to the source arguments. You want the
entire picPlayer picture (not some clipped-out subset of it) so you specify 0, 0 for X and Y and picPlayer ScaleWidth and ScaleHeight again for the width and height. The last argument, SRCCOPY, says just copy the source over of the destination without
regard to what was there before.
Whew! Do you really have to specify each one of those things? No. Here's the short form of the same command.
picBoard.PaintPicture picPlayer.picture, 284, 84, , , , , _ , , SRCCOPY
You do have to specify the source, destination, where on the board to put it, and how it should be placed there (SRCCOPY). But that's it, the defaults cover us for the rest of it.
Don't you run into the second objection with the PaintPicture method, too? Doesn't it just move rectangular areas of memory from one place to another? The answer is yes. It only moves rectangular areas, but the last argument of the PaintPicture method
leaves an open door for us to do more.
The last argument in PaintPicture is a bitwise operator. Table 10.1 showed possible values you could assign to it, most of them confusing at best. How does it work?
Suppose your background image is one of a living room. You want to place a round ball on the sofa without removing a square chunk of the sofa in the process. The easiest way to solve this problem is with a two-step process.
First, you must create what is called a mask. A mask is a monochrome copy of your bitmap where everything in the picture is black and everything that should be transparent is white. For our purposes, the ball would be painted completely flat back so it
was only a silhouette of its former self on a white background.
Now you execute a PaintPicture method for the destination object. The source is the mask image. The location is where you want to place the ball (on the sofa), and the bitwise operator is SRCAND. Look back at that confusing table. It tells you that
SRCAND does a bitwise AND of the destination and the source bytes. White is represented by the highest possible bit values (all 1s), while black is all 0s. Anything ANDed with white is itself. Anything ANDed with black is black. So what does this do? It
makes a perfectly ball-shaped black hole in our sofa!
But that didn't solve our problem. Now we have no sprite on the screen and a great big hole in the couch. Don't worry, you can work this out.
Take your original bitmap (the one with a picture of a real ball on it) and change it so that everything that is not a ball is black. Flood-fill the background using your favorite picture editing program. Filling gives you a regular ball on a black
background as your source. Using the SRCPAINT bitwise operator, do a PaintPicture to indicate the destination (the living room) with this ball in the correct location. If you refer back to Table 10.1, you see this basically performs an OR operation on all
the bytes of color from the source to the destination. Black is zero, anything ORed with zero is itself. The background remains the same (because the area outside the ball is black), and the ball is placed neatly in the black spot, because its bits are
ORed with the black of the hole.
Very nice. Now you have a sprite. Almost.
You have an irregularly shaped picture superimposed on another picture. You would have a sprite. If sprites never moved.
But sprites do move. They usually move a lot. And all that movement can cause problems. Most notably, big black spots or remnants of sprites gone by left on the background. So you want to save the background in a safe place to restore later when the
sprite moves on.
Here's a test of your understanding of the PaintPicture method. What does the following code do?
picWork.PaintPicture picBoard.picture, 0, 0, _ , , 284, 84, , , SRCCOPY picSave.PaintPicture picWork.picture, 0, 0, _ , , , , , , SRCCOPY picWork.PaintPicture picMask.picture, 0, 0, _ , , , , , , SRCAND picWork.PaintPicture picPic.picture, 0, 0, _ , , , , , , SRCPAINT picBoard.PaintPicture picWork.picture, 284, 84, _ , , , , , , SRCCOPY
There are five picture controls: picWork, picBoard, picSave, picMask, and picPic. The sprite is picPic and picMask is its associated mask. The game board where all the action takes place is picBoard. picSave is where to save the background image for use
later. picWork is another bitmap, the same size as picPic and picMask to serve as an off-screen work area. Is that enough hints?
Here's how the code works.
Pretty spiffy. But why go to all the extra trouble of a work area? It turns out that the number of times you copy images to the visible screen is important. If we did all these operations right on the screen, you probably wouldn't notice a difference,
but if we took this same bitmap and started moving it across the screen, redrawing each step of the way, you would start to see the image flicker in and out. You have probably even seen this effect on some video games when there are a lot of sprites moving
at once. This happened a lot on the old home game systems like the Atari 2600.
By moving most of the work to an off-screen buffer, you can make the animation much snappier. You do all the work in the back room and just present it when you're done.
Animation is more than a static image moving around the screen. Animation means that the image itself is alive with realistic (or not-so-realistic) movement. Just like in the movies, this is done by rapidly flipping through pre-created frames quickly
enough that the eye perceives motion.
Easy, right? You set the timer to go off 10 or 15 times a second and in the timer event you do this:
Static iFrame As Integer iFrame = iFrame + 1 If iFrame > 10 Then iFrame = 0 picClown.Picture = LoadPicture(Clown$(iFrame))
Assuming the Clown$ array is filled with filenames, this works, right? Wrong. There is no disk drive anywhere that allows you to read in a new file (of any size) 15 times a second. LoadPicture() will be much too slow for this.
Instead, you probably should load your pictures off-screen ahead of time in their own picture controls. Then, rapidly switch among them. Assuming you had a control array of picture boxes called picClownFrame, this code would work fine.
Static iFrame As Integer iFrame = iFrame + 1 If iFrame > 10 Then iFrame = 0 picClown.Picture = picClownFrame(iFrame)
All of this put together begins to get complicated. It's true that graphics applications, particularly games, are not simple beasts. But there is relief, and it comes in the form of a control. The ImageList control.
On first glance the ImageList control is interesting. A single control who's primary job is to contain a collection of unseen images. The images are indexed by number and, if you like, a key string. There's a handy dialog provided to set them at design
time or you can use the Add method of the collection to add new images at runtime. This sounds perfect for animation. There are a couple of catches:
The image control is much more than an image warehouse. It has two key methods that make the animator's life much easier.
The syntax for the Overlay method looks like this.
ListImageObject.Overlay (IndexKey1, IndexKey2)
IndexKey1 is the numeric index or string key used to specify the image to be overlain.
IndexKey2 is the numeric index or string key used to specify the image to be laid over IndexKey1, using the MaskColor property to identify what color is transparent.
The Overlay method is a function that returns a composite of the two images. This is made possible through an ImageList property called MaskColor. You simply fill in MaskColor with the image color that is supposed to be made transparent. The first image
is then laid over the second image.
This is an interesting prospect, and it holds promise. It could have been used in the code we created above to combine our picPic image with the portion of the picBoard that we copied out to the work area.
What are the benefits? You don't have to go through the tedious process of creating a mask yourself. The ImageList creates an associated mask for every image it holds by using the MaskColor property. Then it automatically does the hole punching and
copying that is necessary to create a combined image.
Believe it or not, there is an even better solution to our animation woes.
The Draw method takes one of our stored images and draws it on any given device context we care to give it. Its syntax looks like this.
ListImageObject.Draw (DestinationhDC, DestinationX, DestinationY, DrawingStyle)
DestinationhDC is the device context of any control. A device context is just a number used by Win32 to address the drawing area on a particular window or control. When you deal with Win32 graphics commands, you always work with device contexts instead
of passing picture controls around or assigning picture properties as you might do in Visual Basic programming. Visual Basic provides access to the device context through the attribute hDC which can be found on the form, PictureBox, and printer objects.
DestinationX and DestinationY indicate the coordinates on the destination picture where the image is to be placed. The default is 0,0.
DrawingStyle determines how the image will be drawn. Table 10.2 lists options for DrawingStyle.
Constant |
Value |
Description |
imlNormal |
0 |
Draws the image over the destination, just as it is. |
imlTransparent |
1 |
Uses the MaskColor method to superimpose the image on the destination so that the parts colored with the value specified in MaskColor are invisible. |
imlSelected |
2 |
The image is dithered with the system highlight color to look as if it is selected. |
imlFocus |
3 |
The image is both dithered and striped with the system highlight color to appear as if it has the focus. |
The exciting one, for our purposes is, of course, imgTransparent. This means that in one call, you can superimpose an image on a destination without ever having to create your own mask image. It keeps one internally for each image and does all the hard
work for you.
This chapter has been a quick introduction to the concepts of animation and how it can be implemented in Visual Basic 4.0. The topic and associated examples could (and will) fill volumes. But the foundation you have gained here will give you a shove off in the right direction for experimentation on your own and a foundation for future study.